package com.epam.task.web.db;

import com.epam.task.web.util.logging.LoggerUtil;
import org.apache.log4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.CopyOnWriteArrayList;

public class ConnectionsPool {

    private static ConnectionsPool instance = null;
    LoggerUtil logger = new LoggerUtil(Logger.getLogger(ConnectionsPool.class));
    private CopyOnWriteArrayList<ConnectionToDB> connections;
    private String driverName;
    private String url;
    private String username;
    private String password;
    private long cleaningInterval;
    private long maxIdleTime;
    private boolean poolIsEmpty;
    private PoolCleaner poolCleaner;

    private ConnectionsPool(String url, String driverName, String username, String password) {
        this.url = url;
        this.driverName = driverName;
        this.username = username;
        this.password = password;
        this.connections = new CopyOnWriteArrayList<>();
        cleaningInterval = 10 * 000;
        maxIdleTime = 10 * 000;
        poolIsEmpty = false;
    }

    public static ConnectionsPool getInstance(String url, String driverName, String username, String password) {
        if (instance == null) {
            instance = new ConnectionsPool(url, driverName, username, password);
        }
        return instance;
    }

    public Connection createConnection() {
        try {
            if (this.poolIsEmpty) {
                throw new SQLException("Connections poll is empty.");
            }

            ConnectionToDB connectionFomPool = null;

            for (ConnectionToDB conn : this.connections) {
                connectionFomPool = conn;

                if (connectionFomPool.use()) {
                    return connectionFomPool.getConnection();
                } else {
                    boolean isHealthy = true;

                    try {
                        if (connectionFomPool.isClosed() && connectionFomPool.getWarnings() != null) {
                            isHealthy = false;
                        }
                    } catch (SQLException e) {
                        isHealthy = false;
                        logger.getExceptionTextFileLogger().error(e);
                    }

                    if (isHealthy) {
                        return connectionFomPool.getConnection();
                    } else {
                        connectionFomPool.expire();
                        connections.remove(connectionFomPool);
                    }
                }
            }

            ConnectionToDB connectionToDB = null;
            try {
                Class.forName(this.driverName);
                Connection sqlConnection = DriverManager.getConnection(this.url, this.username, this.password);
                connectionToDB = new ConnectionToDB(sqlConnection);
                connectionToDB.use();


                this.connections.add(connectionToDB);

                if (this.poolCleaner == null) {
                    this.poolCleaner = new PoolCleaner(cleaningInterval);
                    this.poolCleaner.start();
                }

            } catch (SQLException e) {
                logger.getExceptionTextFileLogger().error(e);
            }

            return connectionToDB.getConnection();
        } catch (SQLException | ClassNotFoundException e) {
            return null;
        }
    }

    public void removePool() {
        ConnectionToDB connection;
        long maxIdleDeadLine = System.currentTimeMillis() - this.maxIdleTime;

        for (int i = this.connections.size() - 1; i >= 0; i--) {
            connection = connections.get(i);

            if (!connection.isInUse() &&
                    (connection.getTimeClosed() < maxIdleDeadLine)) {
                connection.expire();
                connections.remove(i);
            }
        }

        if (this.connections.isEmpty() && this.poolCleaner != null) {
            this.poolCleaner.toStop();
            this.poolCleaner = null;
        }
    }

    class PoolCleaner extends Thread {

        private long cleaningInterval;
        private boolean mustStop;

        public PoolCleaner(long cleaningInterval) {
            this.mustStop = false;
            this.cleaningInterval = cleaningInterval;
            setDaemon(true);
        }

        @Override
        public void run() {
            while (!this.mustStop) {
                try {
                    sleep(this.cleaningInterval);
                } catch (InterruptedException exception) {
                }

                if (this.mustStop) {
                    break;
                }
                removePool();
            }
        }

        public void toStop() {
            this.mustStop = true;
            synchronized (this) {
                this.interrupt();
            }
        }
    }

}
